package ast;

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;

public class WhileStmt extends Stmt {
    public final CondExpr condExpr;
    public final Stmt body;

    public WhileStmt(CondExpr e, Stmt s, Location loc) {
        super(loc);
        condExpr = e;
        body = s;
    }

    public void print(PrintStream ps, String indent) {
        ps.print(indent + "while (");
        condExpr.print(ps);
        ps.print(")\n");
        body.print(ps, indent + "  ");
    }

    public void print(PrintStream ps) {
        print(ps, "");
    }

    // interpret: while the condition is true, keep executing the body, if the condition is false, stop
    public void execute(Stack<Map<String, AbstractValue>> stateStack, Scanner scanner) throws Exception {
        // evaluate the condition
        AbstractBoolValue cond = condExpr.evaluate(stateStack, scanner);
        if (cond == AbstractBoolValue.False) {
            // dead code
            throw new exceptions.ExitDeadCodeException("The body of the while statement is dead code.");
        }
        // in a loop, abs_exec(body, current state) and merge the current state σak into the result state σ′ak. stop after convergence is seen with σ′ak = σ′a(k-1)  and then return σ′a

        // 0 iterations: σ′a = σa, ONLY THE TOP MAP MATTERS
        Stack<Map<String, AbstractValue>> resultStateStack = new Stack<Map<String, AbstractValue>>();
        resultStateStack.addAll(stateStack);
        // 1st iteration:
        body.execute(stateStack, scanner);
        // current state = σa1 is the result of the 1st iteration, ONLY THE TOP MAP MATTERS
        Stack<Map<String, AbstractValue>> curStateStack = new Stack<Map<String, AbstractValue>>();
        curStateStack.addAll(stateStack);
        // refresh the result state to σ′a1, THIS IS ONLY THE TOP MAP
        Map<String, AbstractValue> newResultState = mergeStates(resultStateStack.peek(), curStateStack.peek());

        // while the result state is not the same as the new result state
        while (!resultStateStack.peek().equals(newResultState)) {
            // make a copy of the current state: create the result state
            resultStateStack.pop();
            resultStateStack.push(new HashMap<String, AbstractValue>(newResultState));
            // execute the block body
            body.execute(curStateStack, scanner);

            // merge the current state σak into the result state σ′ak
            curStateStack.pop();
            curStateStack.push(new HashMap<String, AbstractValue>(stateStack.peek()));
            newResultState = mergeStates(resultStateStack.peek(), curStateStack.peek());
            // stop after convergence is seen with σ′ak = σ′a(k-1)  and then return σ′a
        }
        stateStack.peek().putAll(newResultState);
    }

    /**
     // interpret: while the condition is true, keep executing the body, if the condition is false, stop
     public void execute(Map<String, AbstractValue> state, Scanner scanner) throws Exception {
         // in a loop, abs_exec(body, current state) and merge the current state σak into the result state σ′ak. stop after convergence is seen with σ′ak = σ′a(k-1)  and then return σ′a

         // 0 iterations: σ′a = σa
         Map<String, AbstractValue> resultState = new java.util.HashMap<String, AbstractValue>(state);
         // 1st iteration:
         body.execute(state, scanner);
         // current state = σa1 is the result of the 1st iteration
         Map<String, AbstractValue> curState = new java.util.HashMap<String, AbstractValue>(state);
         // refresh the result state to σ′a1
         Map<String, AbstractValue> newResultState = mergeStates(resultState, curState);

         while (!resultState.equals(newResultState)) {
             // make a copy of the current state: create the result state
             resultState = new java.util.HashMap<String, AbstractValue>(newResultState);
             // execute the block body
             body.execute(curState, scanner);

             // merge the current state σak into the result state σ′ak
             curState = new java.util.HashMap<String, AbstractValue>(state);
             newResultState = mergeStates(resultState, curState);
             // stop after convergence is seen with σ′ak = σ′a(k-1)  and then return σ′a
         }
         state.putAll(newResultState);
     }
     * */

    //TODO: merge the current state into the result state
    private Map<String, AbstractValue> mergeStates(java.util.Map<String, AbstractValue> resultState, java.util.Map<String, AbstractValue> curState) {
        Map<String, AbstractValue> newResultState = new java.util.HashMap<String, AbstractValue>(resultState);
        for (String key : curState.keySet()) {
            if (newResultState.containsKey(key)) {
                // if they agree, keep the value
                if (curState.get(key) == newResultState.get(key)) {
                    newResultState.put(key, curState.get(key));
                    // if they disagree, make it AnyInt or AnyFloat
                } else {
                    // if float family, AnyFloat
                    if (curState.get(key) == AbstractValue.NegFloat || curState.get(key) == AbstractValue.ZeroFloat || curState.get(key) == AbstractValue.PosFloat || curState.get(key) == AbstractValue.AnyFloat) {
                        newResultState.put(key, AbstractValue.AnyFloat);
                    }
                    // if int family, AnyInt
                    else if (curState.get(key) == AbstractValue.NegInt || curState.get(key) == AbstractValue.ZeroInt || curState.get(key) == AbstractValue.PosInt || curState.get(key) == AbstractValue.AnyInt) {
                        newResultState.put(key, AbstractValue.AnyInt);
                    }
                }
            }
        }
        return newResultState;
    }
}
